#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "../job.h"

// int siginfo = 1;
int fifo;  // fd of pipe
int globalfd;

std::list<waitqueue> queue;
/* removed from queue */
std::list<waitqueue>::iterator current = queue.end();  // hold a job and run
std::list<waitqueue>::iterator next = queue.end();     // next to hold

void schedule() {
  // jobinfo *newjob = NULL;
  jobcmd cmd;
  int count = 0;

  bzero(&cmd, DATALEN);
  if ((count = read(fifo, &cmd, DATALEN)) < 0) error_sys("read fifo failed");

#ifdef DEBUG

  if (count) {
    printf(
        "cmd cmdtype\t%d\n"
        "cmd defpri\t%d\n"
        "cmd data\t%s\n",
        cmd.type, cmd.defpri, cmd.data);
  }  // else
     //	printf("no data read\n");

#endif

  switch (cmd.type) {
    case ENQ:
      do_enq(cmd);
      break;
    case DEQ:
      do_deq(cmd);
      break;
    case STAT:
      do_stat();
      break;
    default:
      break;
  }
  /* Update jobs in waitqueue */

  updateall();

  /* select the highest priority job to run */

  next = jobselect();

  /* stop current job, run next job */

  jobswitch();
}

int allocjid() {
  static int jobid = 0;
  return ++jobid;
}

void updateall() {
  /* update running job's run_time */
  if (current != queue.end())
    current->job->run_time += 1; /* add 1 represent 100 ms */
  if (next != queue.end()) {
    next->job->wait_time += 100;
    if (next->job->wait_time > 999 && next->job->curpri < 3)
      ++next->job->curpri;
  }
  /* update ready job's wait_time */
  for (auto p = queue.begin(); p != queue.end(); ++p) {
    p->job->wait_time += 100;
    if (p->job->wait_time >= 1000 && p->job->curpri < 3) ++p->job->curpri;
  }
}

/**
 * @brief get the most prior one
 */
std::list<waitqueue>::iterator jobselect() {
  std::list<waitqueue>::iterator p, prev, select, selectprev;
  int highest = -1;

  select = selectprev = queue.end();

  if (queue.empty() == false) {
    prev = p = queue.begin();
    for (; p != queue.end(); prev = p, ++p) {
      if (p->job->curpri > highest) {
        select = p;
        selectprev = prev;
        highest = p->job->curpri;
      }
    }

    if (queue.end() != select) queue.erase(select);
  }

  return select;
}

void jobswitch() {
  struct waitqueue *p;
  int i;

  if (queue.end() != current &&
      current->job->state == DONE) { /* current job finished */
    /* job has been done, remove it */
    freequeue(current);
    // free(current);

    current = queue.end();
  }

  if (next == queue.end() && current == queue.end()) /* no job to run */

    return;

  else if (next != queue.end() && current == queue.end()) { /* start new job */

    printf("begin start new job\n");
    current = next;  // mayber first assign
    next = queue.end();
    current->job->state = RUNNING;
    kill(current->job->pid, SIGCONT);
    return;

  } else if (next != queue.end() && current != queue.end()) { /* do switch */

    kill(current->job->pid, SIGSTOP);
    current->job->curpri = current->job->defpri;
    current->job->wait_time = 0;
    current->job->state = READY;

    /* move back to the queue */

    queue.emplace_back(current);

    current = next;
    next = queue.end();
    current->job->state = RUNNING;
    kill(current->job->pid, SIGCONT);

    // printf("\nbegin switch: current jid=%d, pid=%d\n",
    //		 current->job->jid, current->job->pid);
    return;

  } else { /* next == NULL && current != NULL, no switch */

    return;
  }
}

void sig_handler(int sig, siginfo_t *info, void *notused) {
  int status;
  int ret;

  switch (sig) {
    case SIGVTALRM: /* run out of time and Reschedule */
      schedule();
      return;

    case SIGCHLD:
      ret = waitpid(-1, &status, WNOHANG);
      if (ret == 0 || ret == -1) return; /* error maybe */

      if (WIFEXITED(status)) { /* exited */
#ifdef DEBUG
        printf("%d %d %d\n", ret, info->si_pid, current->job->pid);
        do_stat();
#endif
        current->job->state = DONE;
        printf(
            "normal termation, exit status = %d\tjid = %d, pid = %d, runtime = "
            "%d\n\n",
            WEXITSTATUS(status), current->job->jid, current->job->pid,
            current->job->run_time);

      } else if (WIFSIGNALED(status)) { /* signaled */
        printf(
            "abnormal termation, signal number = %d\tjid = %d, pid = %d, "
            "runtime = %d\n\n",
            WTERMSIG(status), current->job->jid, current->job->pid,
            current->job->run_time);

      } else if (WIFSTOPPED(status)) { /* stopped */
        printf(
            "child stopped, signal number = %d\tjid = %d, pid = %d, runtime = "
            "%d\n\n",
            WSTOPSIG(status), current->job->jid, current->job->pid,
            current->job->run_time);
      } else {
        printf("Die in a unknown way");
      }
      return;

    default:
      return;
  }
}

void do_enq(struct jobcmd enqcmd) {
  waitqueue *newnode, *p;
  int i = 0, pid;
  char *offset, *argvec, *q;
  char **arglist;
  // sigset_t zeromask;

  // sigemptyset(&zeromask);

  /* fill jobinfo struct */

  jobinfo newjob(allocjid(), enqcmd);

  arglist = (char **)malloc(sizeof(char *) * (enqcmd.argnum + 1));
  newjob.cmdarg = arglist;
  offset = enqcmd.data;
  argvec = enqcmd.data;
  int tmpStrLen = 0;
  while (i < enqcmd.argnum) {  // get the seperated parameters
    if (*offset == ':') {
      *offset++ = '\0';
      tmpStrLen = offset - argvec + 1;
      q = (char *)malloc(tmpStrLen);
      q[tmpStrLen - 1] = 0;
      strcpy(q, argvec);
      arglist[i++] = q;
      argvec = offset;

    } else
      offset++;
  }

  arglist[i] = NULL;

#ifdef DEBUG

  printf("enqcmd argnum %d\n", enqcmd.argnum);
  for (i = 0; i < enqcmd.argnum; i++) printf("parse enqcmd:%s\n", arglist[i]);

#endif

  /* add new job to the queue */
  queue.emplace_back(newjob);
#ifdef DEBUG

  printf("new job added to queue id : %d\n", newjob.jid);

#endif
  /* create process for the job */

  if ((pid = fork()) < 0) error_sys("enq fork failed");

  /* In child process */

  if (pid == 0) {
    // newjob.pid = getpid();

    /* block the child wait for run */

    raise(SIGSTOP);

#ifdef DEBUG

    printf("begin running\n");
    for (i = 0; arglist[i] != NULL; i++) printf("arglist %s\n", arglist[i]);

#endif

    /* dup the globalfile descriptor to stdout */
    dup2(globalfd, STDOUT_FILENO);
    if (execv(arglist[0], arglist) < 0) printf("exec failed\n");

    exit(1);

  } else {  // father process
    // newjob->pid = pid;
    printf("\nnew job: jid=%d, pid=%d\n", newjob.jid, newjob.pid);
  }
}

/* bug to fix */
void do_deq(jobcmd deqcmd) {
  int deqid, i;
  decltype(next) p;

  deqid = atoi(deqcmd.data); /* get jid */

#ifdef DEBUG
  printf("deq jid %d\n", deqid);
#endif

  /* current jodid == deqid, terminate current job */
  if (queue.end() != current && current->job->jid == deqid) {
    printf("terminate job: %d\n", current->job->jid);
    kill(SIGTERM, current->job->pid);

    freequeue(current);
    // queue.erase(current);
    current = queue.end();
    // free(current);

  } else { /* maybe in waitqueue, search it */
    if (queue.empty() == false) {
      for (p = queue.begin(); p != queue.end(); ++p) {
        if (p->job->jid == deqid) {
          printf("kill job: %d\n", deqid);
          break;
        }
      }
      if (p != queue.end()) {
        freequeue(p);
        // free(select);
        queue.erase(p);
      }
    }
  }
}

void do_stat() {
  /*
   * Print job statistics of all jobs:
   * 1. job id
   * 2. job pid
   * 3. job owner
   * 4. job run time
   * 5. job wait time
   * 6. job create time
   * 7. job state
   */

  char timebuf[BUFLEN];

  printf("JID\tPID\tOWNER\tRUNTIME\tWAITTIME\tCREATTIME\tSTATE\n");

  if (queue.end() != current) {
    strcpy(timebuf, ctime(&(current->job->create_time)));
    timebuf[strlen(timebuf) - 1] = '\0';
    printf("%d\t%d\t%d\t%d\t%d\t%s\t%s\n", current->job->jid, current->job->pid,
           current->job->ownerid, current->job->run_time,
           current->job->wait_time, timebuf, "RUNNING");
  }

  for (auto p = queue.begin(); p != queue.end(); ++p) {
    strcpy(timebuf, ctime(&(p->job->create_time)));
    timebuf[strlen(timebuf) - 1] = '\0';
    printf("%d\t%d\t%d\t%d\t%d\t%s\t%s\n", p->job->jid, p->job->pid,
           p->job->ownerid, p->job->run_time, p->job->wait_time, timebuf,
           "READY");
  }

  printf("\n");
}

int main() {
  struct timeval interval;
  struct itimerval newtime, old;
  struct stat statbuf;
  struct sigaction newact, oldact1, oldact2;

  if (stat(FIFO, &statbuf) == 0) {
    /* if fifo file exists, remove it */

    if (remove(FIFO) < 0) error_sys("remove failed");
  }

  if (mkfifo(FIFO, 0640) < 0) {
    error_sys("mkfifo failed");
    return -1;
  }

  /* open fifo in nonblock mode */

  if ((fifo = open(FIFO, O_RDONLY | O_NONBLOCK)) < 0) {
    error_sys("open fifo failed");
    return -1;
  }

  /* open global file for job output */

  if ((globalfd = open("/dev/null", O_WRONLY)) < 0) {
    error_sys("open global file failed");
    return -1;
  }
  /* setup signal handler */
  newact.sa_sigaction = sig_handler;
  sigemptyset(&newact.sa_mask);
  newact.sa_flags = SA_SIGINFO;

  sigaction(SIGCHLD, &newact, &oldact1);  // child die
  sigaction(SIGVTALRM, &newact, &oldact2);

  /* timer interval: 0s, 100ms */

  interval.tv_sec = 0;
  interval.tv_usec = 100;

  newtime.it_interval = interval;
  newtime.it_value = interval;
  setitimer(ITIMER_VIRTUAL, &newtime, &old);

  printf("OK! Scheduler is starting now!!\n");

  while (true)
    ;

  close(fifo);
  close(globalfd);
  return 0;
}
